import $ from "jquery";
import * as THREE from "three";
import { Vector2 } from "three";
import { OrthographicCamera } from "three";
import { CONFIG } from "../../app/config.js";
import { OBJECTS, UTILS, FLAGS } from "../../app/declarations.js";

const debug = CONFIG.debug;

let TOOLS = {
  // static functions

  // todo: split entity creation static functions as object

  create: {
    primitive: (params, helper, tweens) => {
      let valid = false;
      let msg = "TOOLS.create: ";

      let geometry = new THREE.BufferGeometry();
      let out;
      let prop;

      let addTo;
      let object;
      let parentObject3D;

      // helper parameters
      let helperSize = helper > 0 ? helper : undefined;
      params.material = !params.material ? undefined : params.material;
      prop = params.prop;

      switch (params.type) {
        default:
        case "cube":
        case "box":
          // properties : width, height, depth, widthSegments, heightSegments, depthSegments
          geometry = new THREE.BoxGeometry(
            prop.width,
            prop.height,
            prop.depth,
            prop.widthSegments,
            prop.heightSegments,
            prop.depthSegments
          );
          valid = true;
          break;
        case "icosahedron":
        case "icosa":
          // properties : radius, segments
          geometry = new THREE.IcosahedronGeometry(prop.radius, prop.segments);
          valid = true;
          break;
        case "sphere":
          // properties : radius, widthSegments, heightSegments
          geometry = new THREE.SphereGeometry(
            prop.radius,
            prop.widthSegments,
            prop.heightSegments
          );
          valid = true;
          break;
        case "plane":
          // properties : width, height, widthWegments, heightSegments
          geometry = new THREE.PlaneGeometry(
            prop.width,
            prop.height,
            prop.widthSegments,
            prop.heightSegments
          );
          valid = true;
          break;
        case "cylinder":
          // properties : radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded
          geometry = new THREE.CylinderGeometry(
            prop.radiusTop,
            prop.radiusBottom,
            prop.height,
            prop.radialSegments,
            prop.heightSegments,
            prop.openEnded
          );
          valid = true;
          break;
        case "text":
          /*
                    height — Float. Thickness to extrude text. Default is 50.
                    curveSegments — Integer. Number of points on the curves. Default is 12.
                    bevelEnabled — Boolean. Turn on bevel. Default is False.
                    bevelThickness — Float. How deep into text bevel goes. Default is 10.
                    bevelSize — Float. How far from text outline is bevel. Default is 8.
                    bevelSegments — Integer. Number of bevel segments. Default is 3.
                    */

          let splitStr;
          if (prop.text) splitStr = TOOLS.splitStringToArray(prop.text, "\n");

          if (splitStr.length > 1) {
            let tempGeometry;
            let offsetLineHeight = prop.offsetLineHeight
              ? prop.offsetLineHeight
              : 10;

            for (let a = 0; a < splitStr.length; a++) {
              tempGeometry = new THREE.TextGeometry(splitStr[a], prop);
              tempGeometry.translate(0, -a * (prop.size + offsetLineHeight), 0);
              geometry.merge(tempGeometry);
            }
          } else {
            geometry = new THREE.TextGeometry(prop.text, prop);
          }

          valid = true;
          break;
        case "multiline":
          break;
      }

      if (valid) {
        object = new THREE.Mesh(geometry, params.material);
        object.name = params.name ? params.name : "";
      }

      if (tweens) {
        // animate
        object.tweens = [];
        if (debug) console.info(object.tweens);

        for (let a = 0; a < tweens.length; a++) {
          object.tweens[tweens[a].name] = TweenMax.to(
            object[tweens[a].prop],
            tweens[a].time,
            tweens[a].params
          );
        }
      }

      if (valid == true && helperSize > 0) {
        object.add(new THREE.AxesHelper(helperSize));
      }

      addTo = !params.addTo ? undefined : params.addTo;
      if (addTo) addTo.add(object);

      let addedTo_msg = addTo ? "in : " + addTo.name : "";

      if (debug)
        console.info(
          msg +
            '"' +
            object.name +
            '" type: ' +
            object.type +
            " > " +
            params.type +
            addedTo_msg
        );
      return object;
    },

    material: (params) => {
      let valid = false;
      let msg = "TOOLS.create: ";
      let object;

      switch (params.type) {
        case "normal":
          object = new THREE.MeshNormalMaterial(params.prop);
          valid = true;
          break;
        case "basic":
          object = new THREE.MeshBasicMaterial(params.prop);
          valid = true;
          break;
        case "phong":
          object = new THREE.MeshPhongMaterial(params.prop);
          valid = true;
          break;
        case "lambert":
          object = new THREE.MeshLambertMaterial(params.prop);
          valid = true;
          break;
      }

      if (object) object.name = valid && params.name ? params.name : undefined;

      if (debug)
        console.info(
          msg + '"' + object.name + '" type: ' + object.type,
          params.type
        );

      return object;
    },

    light: (params) => {
      let valid = false;
      let object;
      let msg = "TOOLS.create: ";

      switch (params.type) {
        case "point":
          break;
      }

      if (object) object.name = valid && params.name ? params.name : undefined;
      if (debug)
        console.info(
          msg + '"' + object.name + '" type: ' + object.type,
          params.type
        );
      return object;
    },

    geometry: (params) => {},

    importedMesh: (params) => {},
  },

  getDimensions: (object) => {
    let regexpPattern1 = /geometry/gim;
    let regexpPattern2 = /mesh/gim;

    let isGeometry = object.type.match(regexpPattern1) ? true : false;
    let isMesh = object.type.match(regexpPattern2) ? true : false;

    let geometry;
    let dimensions = {};
    let boundingBox;

    if (isMesh && !isGeometry) geometry = object.geometry;

    if (!isMesh && isGeometry) geometry = object;

    if (isMesh || isGeometry) {
      geometry.computeBoundingBox();
      boundingBox = geometry.boundingBox;
      dimensions.x = Math.abs(boundingBox.min.x) + Math.abs(boundingBox.max.x);
      dimensions.y = Math.abs(boundingBox.min.y) + Math.abs(boundingBox.max.y);
      dimensions.z = Math.abs(boundingBox.min.z) + Math.abs(boundingBox.max.z);
      return dimensions;
    } else {
      return false;
    }
  },

  randRange: function (min, max, round) {
    if (round) {
      return Math.floor(Math.random() * (max - min) + min);
    } else {
      return Math.random() * (max - min) + min;
    }
  },

  d2r: function (val) {
    return (val * Math.PI) / 180;
  },

  r2d: function (val) {
    return (val * 180) / Math.PI;
  },

  // remap: function (value, from1, to1, from2, to2) {
  //   return ((value - from1) / (to1 - from1)) * (to2 - from2) + from2;
  // },

  remap: (value, minInput, maxInput, minOutput, maxOutput) => {
    value = Math.min(Math.max(value, minInput), maxInput);

    return (
      ((value - minInput) * (maxOutput - minOutput)) / (maxInput - minInput) +
      minOutput
    );
  },

  remap2: (val, max, min) => (val - min) / (max - min),

  clamp: function (input, min, max, round) {
    let res;
    if (round) {
      res = Math.round(Math.min(Math.max(input, min), max));
    } else {
      res = Math.min(Math.max(input, min), max);
    }

    return res;
  },

  // todo
  lerp: function () {},

  // lowPassFlter: (val)=>{
  //   const filtered = alpha * val + (1 - alpha) * filteredX;
  //   return filtered;
  // },

  adaptWindow: function (camera) {
    if (!camera) OBJECTS.currentCamera;

    UTILS.sw = $(window).width();
    UTILS.sh = $(window).height();

    let aspect = UTILS.sw / UTILS.sh;
    const frustumSize = CONFIG.camera.world.frustumSize;
    OBJECTS.camera.aspect = aspect;
    OBJECTS.orientation = UTILS.sh > UTILS.sw ? "portrait" : "landscape";
    switch (camera.type) {
      default:
      case "PerspectiveCamera":
        // console.log(camera.type)
        OBJECTS.renderer.setSize(UTILS.sw, UTILS.sh);

        if (CONFIG.postprocessing.enabled == true) {
          OBJECTS.postprocessing.composer.setSize(UTILS.sw, UTILS.sh);
        } else {
          OBJECTS.renderer.setSize(UTILS.sw, UTILS.sh);
        }

        break;
      case "OrthographicCamera":
        if (CONFIG.postprocessing.enabled == true) {
          OBJECTS.postprocessing.composer.setSize(UTILS.sw, UTILS.sh);
        } else {
          console.log("OrthographicCamera");
          OBJECTS.renderer.setSize(UTILS.sw, UTILS.sh);
        }

        OBJECTS.camera.left = (frustumSize * aspect) / -2;
        OBJECTS.camera.right = (frustumSize * aspect) / 2;
        OBJECTS.camera.top = frustumSize / 2;
        OBJECTS.camera.bottom = frustumSize / -2;
        break;
    }
    OBJECTS.camera.updateProjectionMatrix();
    OBJECTS.camera.updateMatrixWorld(true);
  },

  splitStringToArray: function (val, splitBy) {
    if (val.length > 0 && splitBy.length > 0) {
      let out = val.split(splitBy);
      return out;
    }
  },

  checkRegexp: function (val, type) {
    val = val.toString();
    var pattern;
    var res;
    switch (type) {
      default:
      case "num":
        pattern = /^[0-9]*$/;
        break;
      case "phone":
        pattern = /^1[0-9]{10}$/;
        break;
    }

    var res = val.match(pattern) ? true : false;
    return res;
  },

  UV2: {
    generateTopUV: function (geometry, vertices, idxA, idxB, idxC) {
      var ax, ay, bx, by, cx, cy;

      ax = vertices[idxA * 3];
      ay = vertices[idxA * 3 + 1];
      bx = vertices[idxB * 3];
      by = vertices[idxB * 3 + 1];
      cx = vertices[idxC * 3];
      cy = vertices[idxC * 3 + 1];
      return [
        new THREE.Vector2(ax, ay),
        new THREE.Vector2(bx, by),
        new THREE.Vector2(cx, cy),
      ];
    },
    generateSideWallUV: function (geometry, vertices, idxA, idxB, idxC, idxD) {
      return [
        new THREE.Vector2(0, 0),
        new THREE.Vector2(1, 0),
        new THREE.Vector2(1, 1),
        new THREE.Vector2(0, 1),
      ];
    },
  },

  resetUV: (obj) => {
    var pos = obj.geometry.getAttribute("position"),
      nor = obj.geometry.getAttribute("normal"),
      uvs = obj.geometry.getAttribute("uv");

    for (var i = 0; i < pos.count; i++) {
      var x = 0,
        y = 0;

      var nx = Math.abs(nor.getX(i)),
        ny = Math.abs(nor.getY(i)),
        nz = Math.abs(nor.getZ(i));

      // if facing X
      if (nx >= ny && nx >= nz) {
        x = pos.getZ(i);
        y = pos.getY(i);
      }

      // if facing Y
      if (ny >= nx && ny >= nz) {
        x = pos.getX(i);
        y = pos.getZ(i);
      }

      // if facing Z
      if (nz >= nx && nz >= ny) {
        x = pos.getX(i);
        y = pos.getY(i);
      }

      uvs.setXY(i, x, y);
    }
  },

  UV: {
    generateTopUV: function (geometry, vertices, indexA, indexB, indexC) {
      const a_x = vertices[indexA * 3];
      const a_y = vertices[indexA * 3 + 1];
      const b_x = vertices[indexB * 3];
      const b_y = vertices[indexB * 3 + 1];
      const c_x = vertices[indexC * 3];
      const c_y = vertices[indexC * 3 + 1];

      return [
        new Vector2(a_x, a_y),
        new Vector2(b_x, b_y),
        new Vector2(c_x, c_y),
      ];
    },

    generateBoxProjection: function (
      geom,
      transformMatrix,
      bbox,
      bbox_max_size
    ) {
      let coords = [];
      coords.length = (2 * geom.attributes.position.array.length) / 3;

      // geom.removeAttribute('uv');
      if (geom.attributes.uv === undefined) {
        geom.addAttribute("uv", new THREE.Float32BufferAttribute(coords, 2));
      }

      //maps 3 verts of 1 face on the better side of the cube
      //side of the cube can be XY, XZ or YZ
      let makeUVs = function (v0, v1, v2) {
        //pre-rotate the model so that cube sides match world axis
        v0.applyMatrix4(transformMatrix);
        v1.applyMatrix4(transformMatrix);
        v2.applyMatrix4(transformMatrix);

        //get normal of the face, to know into which cube side it maps better
        let n = new THREE.Vector3();
        n.crossVectors(v1.clone().sub(v0), v1.clone().sub(v2)).normalize();

        n.x = Math.abs(n.x);
        n.y = Math.abs(n.y);
        n.z = Math.abs(n.z);

        let uv0 = new THREE.Vector2();
        let uv1 = new THREE.Vector2();
        let uv2 = new THREE.Vector2();
        // xz mapping
        if (n.y > n.x && n.y > n.z) {
          uv0.x = (v0.x - bbox.min.x) / bbox_max_size;
          uv0.y = (bbox.max.z - v0.z) / bbox_max_size;

          uv1.x = (v1.x - bbox.min.x) / bbox_max_size;
          uv1.y = (bbox.max.z - v1.z) / bbox_max_size;

          uv2.x = (v2.x - bbox.min.x) / bbox_max_size;
          uv2.y = (bbox.max.z - v2.z) / bbox_max_size;
        } else if (n.x > n.y && n.x > n.z) {
          uv0.x = (v0.z - bbox.min.z) / bbox_max_size;
          uv0.y = (v0.y - bbox.min.y) / bbox_max_size;

          uv1.x = (v1.z - bbox.min.z) / bbox_max_size;
          uv1.y = (v1.y - bbox.min.y) / bbox_max_size;

          uv2.x = (v2.z - bbox.min.z) / bbox_max_size;
          uv2.y = (v2.y - bbox.min.y) / bbox_max_size;
        } else if (n.z > n.y && n.z > n.x) {
          uv0.x = (v0.x - bbox.min.x) / bbox_max_size;
          uv0.y = (v0.y - bbox.min.y) / bbox_max_size;

          uv1.x = (v1.x - bbox.min.x) / bbox_max_size;
          uv1.y = (v1.y - bbox.min.y) / bbox_max_size;

          uv2.x = (v2.x - bbox.min.x) / bbox_max_size;
          uv2.y = (v2.y - bbox.min.y) / bbox_max_size;
        }

        return {
          uv0: uv0,
          uv1: uv1,
          uv2: uv2,
        };
      };

      if (geom.index) {
        // is it indexed buffer geometry?
        for (let vi = 0; vi < geom.index.array.length; vi += 3) {
          let idx0 = geom.index.array[vi];
          let idx1 = geom.index.array[vi + 1];
          let idx2 = geom.index.array[vi + 2];

          let vx0 = geom.attributes.position.array[3 * idx0];
          let vy0 = geom.attributes.position.array[3 * idx0 + 1];
          let vz0 = geom.attributes.position.array[3 * idx0 + 2];

          let vx1 = geom.attributes.position.array[3 * idx1];
          let vy1 = geom.attributes.position.array[3 * idx1 + 1];
          let vz1 = geom.attributes.position.array[3 * idx1 + 2];

          let vx2 = geom.attributes.position.array[3 * idx2];
          let vy2 = geom.attributes.position.array[3 * idx2 + 1];
          let vz2 = geom.attributes.position.array[3 * idx2 + 2];

          let v0 = new THREE.Vector3(vx0, vy0, vz0);
          let v1 = new THREE.Vector3(vx1, vy1, vz1);
          let v2 = new THREE.Vector3(vx2, vy2, vz2);

          let uvs = makeUVs(v0, v1, v2, coords);

          coords[2 * idx0] = uvs.uv0.x;
          coords[2 * idx0 + 1] = uvs.uv0.y;

          coords[2 * idx1] = uvs.uv1.x;
          coords[2 * idx1 + 1] = uvs.uv1.y;

          coords[2 * idx2] = uvs.uv2.x;
          coords[2 * idx2 + 1] = uvs.uv2.y;
        }
      } else {
        for (let vi = 0; vi < geom.attributes.position.array.length; vi += 9) {
          let vx0 = geom.attributes.position.array[vi];
          let vy0 = geom.attributes.position.array[vi + 1];
          let vz0 = geom.attributes.position.array[vi + 2];

          let vx1 = geom.attributes.position.array[vi + 3];
          let vy1 = geom.attributes.position.array[vi + 4];
          let vz1 = geom.attributes.position.array[vi + 5];

          let vx2 = geom.attributes.position.array[vi + 6];
          let vy2 = geom.attributes.position.array[vi + 7];
          let vz2 = geom.attributes.position.array[vi + 8];

          let v0 = new THREE.Vector3(vx0, vy0, vz0);
          let v1 = new THREE.Vector3(vx1, vy1, vz1);
          let v2 = new THREE.Vector3(vx2, vy2, vz2);

          let uvs = makeUVs(v0, v1, v2, coords);

          let idx0 = vi / 3;
          let idx1 = idx0 + 1;
          let idx2 = idx0 + 2;

          coords[2 * idx0] = uvs.uv0.x;
          coords[2 * idx0 + 1] = uvs.uv0.y;

          coords[2 * idx1] = uvs.uv1.x;
          coords[2 * idx1 + 1] = uvs.uv1.y;

          coords[2 * idx2] = uvs.uv2.x;
          coords[2 * idx2 + 1] = uvs.uv2.y;
        }
      }

      geom.attributes.uv.array = new Float32Array(coords);
    },

    applyBoxProjection: function (bufferGeometry, transformMatrix, boxSize) {
      if (transformMatrix === undefined) {
        transformMatrix = new THREE.Matrix4();
      }

      if (boxSize === undefined) {
        let geom = bufferGeometry;
        geom.computeBoundingBox();
        let bbox = geom.boundingBox;

        let bbox_size_x = bbox.max.x - bbox.min.x;
        let bbox_size_z = bbox.max.z - bbox.min.z;
        let bbox_size_y = bbox.max.y - bbox.min.y;

        boxSize = Math.max(bbox_size_x, bbox_size_y, bbox_size_z);
      }

      let uvBbox = new THREE.Box3(
        new THREE.Vector3(-boxSize / 2, -boxSize / 2, -boxSize / 2),
        new THREE.Vector3(boxSize / 2, boxSize / 2, boxSize / 2)
      );

      TOOLS.UV.generateBoxProjection(
        bufferGeometry,
        transformMatrix,
        uvBbox,
        boxSize
      );
    },

    generateSideWallUV: function (
      geometry,
      vertices,
      indexA,
      indexB,
      indexC,
      indexD
    ) {
      const a_x = vertices[indexA * 3];
      const a_y = vertices[indexA * 3 + 1];
      const a_z = vertices[indexA * 3 + 2];
      const b_x = vertices[indexB * 3];
      const b_y = vertices[indexB * 3 + 1];
      const b_z = vertices[indexB * 3 + 2];
      const c_x = vertices[indexC * 3];
      const c_y = vertices[indexC * 3 + 1];
      const c_z = vertices[indexC * 3 + 2];
      const d_x = vertices[indexD * 3];
      const d_y = vertices[indexD * 3 + 1];
      const d_z = vertices[indexD * 3 + 2];

      if (Math.abs(a_y - b_y) < Math.abs(a_x - b_x)) {
        return [
          new Vector2(a_x, 1 - a_z),
          new Vector2(b_x, 1 - b_z),
          new Vector2(c_x, 1 - c_z),
          new Vector2(d_x, 1 - d_z),
        ];
      } else {
        return [
          new Vector2(a_y, 1 - a_z),
          new Vector2(b_y, 1 - b_z),
          new Vector2(c_y, 1 - c_z),
          new Vector2(d_y, 1 - d_z),
        ];
      }
    },
  },

  adaptAllCameras: () => {
    // console.log(OBJECTS.cameras.length)
    OBJECTS.cameras.forEach((camera) => {
      TOOLS.adaptWindow(camera);
    });
  },

  getAgent: function () {
    let res = "Desktop"; //default to desktop
    if (window.navigator.userAgent.match(/.+iPhone|iPad/gm)) res = "iOS";
    if (window.navigator.userAgent.match(/.+Android/gm)) res = "Android";

    console.log(window.navigator.userAgent);

    return res;
  },

  isMobileDevice: function () {
    // let res = window.navigator.userAgent.match()
  },

  updateOrientation: function () {
    let res =
      $(window).innerWidth() > $(window).innerHeight()
        ? "landscape"
        : "portrait";
    return res;
  },

  roundOffDecimal: function (val) {
    return Math.round(val * 100) / 100;
  },

  returnTimeCountdown: function (ms) {
    let seconds, minutes, hours, days;

    console.log(ms);

    seconds = Math.round(ms / 1000);
    minutes = Math.round(seconds / 60);
    hours = Math.round(minutes / 60);
    days = Math.round(hours / 24);

    hours = hours > 24 ? 24 : hours;
    minutes = minutes > 59 ? 59 : minutes;
    seconds = seconds > 59 ? 59 : seconds;

    return {
      d: days,
      h: hours,
      m: minutes,
      s: seconds,
    };
  },

  getQuaternionFromObject: (object) => {
    const quaternion = new THREE.Quaternion();
    quaternion.copy(object.quaternion);
    return quaternion;
  },

  combineQuaternions: (q1, q2) => {
    const combinedQuaternion = new THREE.Quaternion();
    combinedQuaternion.multiplyQuaternions(q1, q2);
    return combinedQuaternion;
  },
};

export { TOOLS };
